﻿
namespace Patterns4Net.ArchitectureExplorer.Diagram.Services.Relations
{
    using System.IO;
    using System.Linq;
    using Mono.Cecil;
    using ArchitectureExplorer.Diagram.Relations;
    using Attributes;
    using Attributes.Relations;
    using Core.CecilExtensions;

    /// <summary>
    /// This provider is the first from relationships providers,
    /// it adds relations based on pattern roles.
    /// </summary>
    [DiagramUpdaterExport(Order = 1)]
    public class PatternRolesProvider : DiagramVisitor, IDiagramUpdater
    {
        public void UpdateDiagram(Diagram diagram, TextWriter writer)
        {
            this.Diagram = diagram;
            diagram.Accept(this);
        }

        protected Diagram Diagram { get; set; }

        public override void Visit(TypeInfo typeInfo)
        {
            foreach (var patternInfo in typeInfo.Patterns)
            {
                this.Visit(typeInfo, patternInfo);
            }
        }

        private void Visit(TypeInfo typeInfo, PatternInfo patternInfo)
        {
            var roles = patternInfo.Pattern.GetType().GetProperties().Where(
                x => x.GetCustomAttributes(typeof(PatternRoleAttribute), false).Any());
            foreach (var role in roles)
            {
                var value = role.GetValue(patternInfo.Pattern, new object[0]);
                if (value == null)
                {
                    continue;
                }

                var attribute = (PatternRoleAttribute)role.GetCustomAttributes(typeof(PatternRoleAttribute), false).First();
                this.Visit(typeInfo, attribute, role, (dynamic)value);
            }
        }

        private void Visit(TypeInfo type, PatternRoleAttribute role, System.Reflection.PropertyInfo property, object value)
        {
            // this is just catch-up for all non-valid values
        }

        private void Visit(TypeInfo type, PatternRoleAttribute role, System.Reflection.PropertyInfo property, TypeReference otherType)
        {
            var target = this.Diagram.Types.FirstOrDefault(t =>
                    {
                        TypeDefinition targetTypeDef;
                        return t.TryGetTag(out targetTypeDef) && 
                            targetTypeDef != null && 
                            targetTypeDef.IsEqual(otherType);
                    });
            if (target == null)
            {
                return;
            }

            Relation relation = CreateRelation(role.RelationType, type, target, role.Cardinality);
            this.Diagram.AddRelation(relation);

            if (string.IsNullOrWhiteSpace(role.Name))
            {
                relation.Name = property.Name;
            }
            else
            {
                relation.Name = role.Name;
            }
        }

        private Relation CreateRelation(RelationType relationType, DiagramElement source, DiagramElement target, Cardinality targetCardinality)
        {
            switch (relationType)
            {
                case RelationType.Aggregation:
                    return new Aggregation(source, target, targetCardinality);

                case RelationType.Composition:
                    return new Composition(source, target, targetCardinality);

                case RelationType.Inheritance:
                    return new Inheritance(source, target);

                case RelationType.Association:
                    return new Association(source, target, targetCardinality);

                case RelationType.Uses:
                    return Uses.CreateUnidirectional(source, target);

                default:
                    return new Association(source, target, targetCardinality);
            }
        }
    }
}